package com.bj58.mis.apm.server.statistics.suggest.parameterize;

import com.bj58.mis.apm.server.core.util.JsonUtil;
import com.bj58.mis.apm.server.statistics.dto.cube.CubeInfo;
import com.bj58.mis.apm.server.statistics.dto.cube.Segment;
import com.bj58.mis.apm.server.statistics.dto.job.KylinJobInfo;
import com.bj58.mis.apm.server.statistics.dto.optimize.*;
import com.bj58.mis.apm.server.statistics.dto.rowkey.RowkeyStats;
import com.bj58.mis.apm.server.statistics.mapper.CubeMapper;
import com.bj58.mis.apm.server.statistics.mapper.JobMapper;
import com.bj58.mis.apm.server.statistics.mapper.RowkeyStatsMapper;
import com.bj58.mis.apm.server.statistics.mapper.SuggestMapper;
import com.bj58.mis.apm.server.statistics.service.OptimizeService;
import com.bj58.mis.apm.server.statistics.suggest.TurningSuggest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;

/**
 * 调优计算服务
 * @Author: wangxw
 * @Date: 2018/10/20 10:45
 */
@Component
public class TurningSuggestParameterize implements TurningSuggest {

    @Autowired
    private SuggestMapper suggestMapper;

    @Resource
    private CubeMapper cubeMapper;

    @Resource
    private RowkeyStatsMapper rowkeyStatsMapper;

    @Resource
    private JobMapper jobMapper;

    @Resource
    private OptimizeService optimizeService;

    @Override
    public CubeSuggest getCubeSuggest(String cubeId, SuggestFactor factor) {

        if(factor.existsNullValue()){
            factor = setDefault(factor);
        }

        CubeSuggest cubeSuggest = new CubeSuggest();
        //查询维度组合
        List<Map<String, Object>> groupColumns = suggestMapper.queryGroupColumns(cubeId, factor.getFactorDays());

        //计算总查询次数
        Map<String, Integer> times = new HashMap<>();
        Queries queries = calcTimes(groupColumns, factor, times);

        //转换成set集合
        Map<String, Set<String>> groupColumnMap = splitToSet(times.keySet());
        //构建聚合组，以出现不交叉为条件
        List<SuggestGroup> suggestGroupList = buildSuggestGroup(groupColumnMap, times, factor);
        cubeSuggest.setSuggestGroups(suggestGroupList);
        //匹配现有聚合组数据
        matchGroups(suggestGroupList, cubeId);
        //设置分析建议
        setAnalysis(cubeSuggest, queries, cubeId, times, factor);
        //设置调优建议
        setProposal(cubeSuggest, cubeId);

        return cubeSuggest;
    }

    /**
     * 查询默认调优系数
     * @return
     */
    @Override
    public SuggestFactor getSuggestFactor() {
        return suggestMapper.getSuggestFactor();
    }

    private SuggestFactor setDefault(SuggestFactor factor){
        factor.setDefault(getSuggestFactor());
        return factor;
    }

    public Queries calcTimes(List<Map<String, Object>> groupColumns, SuggestFactor factor, Map<String, Integer> times) {
        Map<String, Double> tempTimes = new HashMap<>();
        Long queries = 0L;
        double damping = factor.getFactorDamping();
        Date now = new Date();
        for(Map<String, Object> map : groupColumns) {
            Number num = (Number)map.get("query_times");
            Date date = (Date)map.get("query_date");
            String columns = (String)map.get("group_columns");
            queries += num.longValue();
            int days = getDays(now, date);
            Double temp = tempTimes.get(columns);
            if(temp == null){
                tempTimes.put(columns, calcTimesByDamping(damping, days, num.intValue()));
            }else{
                tempTimes.put(columns, temp + calcTimesByDamping(damping, days, num.intValue()));
            }
        }
        Long effective = 0L;
        for(String key : tempTimes.keySet()){
            if(tempTimes.get(key).intValue() > 0){
                times.put(key, tempTimes.get(key).intValue());
                effective += tempTimes.get(key).longValue();
            }
        }

        return new Queries(queries, effective);
    }

    /**
     * 获取两个日期的相差天数
     * @param now 当前时间，完整
     * @param before 记录日期，只包含了年月日
     * @return
     */
    private int getDays(Date now, Date before){
        return ((Number)Math.floor((now.getTime() - before.getTime())/1000D/3600D/24D)).intValue();
    }

    private double calcTimesByDamping(double damping, int days, int times) {
        return Math.pow(damping, days) * times;
    }

    /**
     * 处理columns字符串成Set集合
     * @param groupColumns
     * @return
     */
    private Map<String, Set<String>> splitToSet(Collection<String> groupColumns){
        Map<String, Set<String>> map = new HashMap<>();
        groupColumns.forEach(col -> {
            map.put(col, new HashSet(Arrays.asList(col.split(","))));
        });
        return map;
    }
    /**
     * 根据查询维度组合情况，计算聚合组
     */
    private List<SuggestGroup> buildSuggestGroup(Map<String, Set<String>> map, Map<String, Integer> times, SuggestFactor factor){

        Double aggFactor = factor.getFactorAggregation();

        Map<String, Integer> aggMap = new HashMap<>();
        Map<String, Set<String>> groupMap = new HashMap<>();
        Map<String, Set<String>> roleMap = new HashMap<>();

        map.keySet().forEach(key -> {
            Set<String> cols = new HashSet(map.get(key));
            if(groupMap.size() > 0){
                List<String> temp = new ArrayList<>();
                groupMap.keySet().forEach(gs -> {
                    if(checkByFactorAggregation(aggMap.get(gs), times.get(key), aggFactor)) {
                        for (String col : cols) {
                            if (groupMap.get(gs).contains(col)) {
                                temp.add(gs);
                                break;
                            }
                        }
                    }
                });
                if(temp.size() == 0) {
                    aggMap.put(key, new Integer(times.get(key)));
                    groupMap.put(key, cols);
                    roleMap.put(key, new HashSet(Arrays.asList(key)));
                }else {
                    int max = times.get(key);
                    Set<String> roleset = new HashSet(Arrays.asList(key));
                    for(String s: temp){
                        cols.addAll(groupMap.remove(s));
                        max = Math.max(max, aggMap.remove(s));
                        roleset.addAll(roleMap.remove(s));
                    }
                    String columns = StringUtils.join(cols.toArray(), ",");
                    aggMap.put(columns, max);
                    groupMap.put(columns, cols);
                    roleMap.put(columns, roleset);
                }
            }else{
                aggMap.put(key, new Integer(times.get(key)));
                groupMap.put(key, cols);
                roleMap.put(key, new HashSet(Arrays.asList(key)));
            }
        });

        List<SuggestGroup> suggestGroupList = new ArrayList<>();

        groupMap.keySet().forEach(key -> {
            SuggestGroup group = new SuggestGroup();
            suggestGroupList.add(group);
            group.setGroupType(SuggestGroupType.SUGGEST_ADD);
            group.setGroupName(getGroupName(suggestGroupList.size()));
            group.setNewGroupColumns(new ArrayList(groupMap.get(key)));

            //构建聚合组内规则
            buildSelectRule(group, roleMap.get(key), map, times, factor);
        });
        return suggestGroupList;
    }

    /**
     * 构建聚合组内规则
     * @param suggestGroup
     * @param keys
     * @param times
     * @param factor
     */
    private void buildSelectRule(SuggestGroup suggestGroup, Set<String> keys, Map<String, Set<String>> map, Map<String, Integer> times, SuggestFactor factor) {
        Double factorMandatory = factor.getFactorMandatory();
        Double factorJoint = factor.getFactorJoint();

        //计算维度出现在查询的次数
        Map<String, Double> mandatoryMap = new HashMap<>();
        Double total = 0d;
        for(String key : keys) {
            total += times.get(key);
            map.get(key).forEach(col -> {
                Double d = mandatoryMap.get(col);
                if(d == null){
                    d = 0d;
                }
                mandatoryMap.put(col, d + times.get(key));
            });
        }

        //处理强制维度
        List<String> mandatoryList = new ArrayList<>();
        for(String k : mandatoryMap.keySet()){
            if(mandatoryMap.get(k) >= total * factorMandatory){
                mandatoryList.add(k);
            }
        }
        suggestGroup.setMandatoryAddDims(mandatoryList);
        
        //处理联合维度
        Map<String, Set<String>> jointMap1 = new HashMap<>();
        Map<String, Double> jointMap = new HashMap<>();
        List<String> columns = suggestGroup.getNewGroupColumns();
        for(int i = 0; i < columns.size(); i++){
            if(mandatoryList.contains(columns.get(i))){
                continue;
            }
            for(int j = i+1; j < columns.size(); j++){
                if(mandatoryList.contains(columns.get(j))){
                    continue;
                }
                Set<String> set = new HashSet<>();
                set.add(columns.get(i));
                set.add(columns.get(j));
                String k = StringUtils.join(set, ",");
                jointMap1.put(k, set);
            }
        }
        jointMap1.keySet().forEach( k -> {
            keys.forEach(s -> {
                if(map.get(s).containsAll(jointMap1.get(k))){
                    Double d = jointMap.get(k);
                    if(d == null){
                        d = 0D;
                    }
                    d += times.get(s);
                    jointMap.put(k, d);
                }
            });
        });
        jointMap1.keySet().forEach(k -> {
            Double d = jointMap.get(k);
            if(d != null) {
                Double t = jointMap1.get(k).stream().mapToDouble(col -> mandatoryMap.get(col)).summaryStatistics().getSum();
                if ((d * 2d / t) < factorJoint) {
                    jointMap.remove(k);
                }
            }
        });

        List<String> list = new ArrayList<>(jointMap.keySet());
        for(int i = 0; i < list.size() ; i++){
            if(jointMap.get(list.get(i)) == null){
                continue;
            }
            for (int j = i + 1; j < list.size(); j++) {
                if (jointMap.get(list.get(j)) == null) {
                    continue;
                }
                if (jointMap.get(list.get(i)).doubleValue() == jointMap.get(list.get(j)).doubleValue()) {
                    Set<String> set1 = new HashSet<>(jointMap1.get(list.get(i)));
                    Set<String> set2 = new HashSet<>(jointMap1.get(list.get(j)));
                    set1.retainAll(set2);
                    if (set1.size() > 0) {
                        jointMap.put(StringUtils.join(set2, ","), jointMap.get(list.get(i)));
                        jointMap.remove(list.get(i));
                        jointMap.remove(list.get(j));
                        break;
                    }
                }
            }
        }
        List<List<String>> jointList = new ArrayList<>();
        jointMap.keySet().forEach(key -> {
            jointList.add(Arrays.asList(key.split(",")));
        });
        suggestGroup.setGroupAddDims(jointList);
    }

    /**
     * 匹配现有聚合组信息，将不完全匹配的进行删除操作
     * @param suggestGroupList
     */
    private void matchGroups(List<SuggestGroup> suggestGroupList, String cubeId){
        //获取定义数据
        Map<String, Object> designMap = cubeMapper.getDesign(cubeId);
        if(designMap == null || designMap.get("groups") == null) {
            return;
        }
        String groupsJson = (String) designMap.get("groups");
        List<Map<String, Object>> groups = JsonUtil.fromArrayJson(groupsJson);

        //构建匹配
        groups.forEach(map -> {
            List<String> includes = (List<String>) map.get("includes");
            SuggestGroup group = null;
            for(SuggestGroup sg : suggestGroupList){
                if(includes.containsAll(sg.getNewGroupColumns()) && sg.getNewGroupColumns().containsAll(includes)){
                    group = sg;
                    break;
                }
            }

            Map<String, Object> selectRule = (Map<String, Object>) map.get("select_rule");
            List<String> mandatoryDims = (List<String>) selectRule.get("mandatory_dims");
            List<List<String>> hierarchyDims = (List<List<String>>) selectRule.get("hierarchy_dims");
            List<List<String>> jointDims = (List<List<String>>) selectRule.get("joint_dims");
            hierarchyDims.addAll(jointDims);

            if(group != null) {
                group.setGroupType(SuggestGroupType.EXISTS);
                List<String> mandatoryAdd = new ArrayList<>(group.getMandatoryAddDims());
                mandatoryAdd.removeAll(mandatoryDims);
                mandatoryDims.removeAll(group.getMandatoryAddDims());

                group.setMandatoryAddDims(mandatoryAdd);
                group.setMandatoryDeleteDims(mandatoryDims);

                List<List<String>> groupAdd = new ArrayList<>(group.getGroupAddDims());
                dealGroup(groupAdd, hierarchyDims);
                dealGroup(hierarchyDims, group.getGroupAddDims());
                group.setGroupAddDims(groupAdd);
                group.setGroupDeleteDims(hierarchyDims);

            }else {
                group = new SuggestGroup();
                suggestGroupList.add(group);
                group.setGroupName(getGroupName(suggestGroupList.size()));
                group.setGroupType(SuggestGroupType.EXISTS_BUT_SUGGEST_DELETE);
                group.setDeleteDims(includes);
                //强制维度
                group.setMandatoryDeleteDims(mandatoryDims);
                //组合维度
                group.setGroupDeleteDims(hierarchyDims);
            }
        });
    }

    /**
     * 设置调优分析
     * @param cubeSuggest
     * @param cubeId
     */
    private void setAnalysis(CubeSuggest cubeSuggest, Queries queries, String cubeId, Map<String, Integer> times, SuggestFactor factor) {
        List<String> analysis = new ArrayList<>();
        cubeSuggest.setAnalysis(analysis);

        ThresholdFactor tf = suggestMapper.getThresholdFactor();
        //膨胀倍数
        CubeInfo cube = cubeMapper.get(cubeId);
        analysis.add(AnalysisUtil.getAnalysisSwell(cube.getSwell(), cube.getDataSize(), tf.getFactorSwell()));

        //查询
        analysis.add(AnalysisUtil.getAnalysisQuery(queries.getTimes().intValue(), queries.getEffectiveTimes().intValue(), tf.getFactorQueries().intValue(), factor.getFactorDamping()));

        //rowkey 情况
        List<RowkeyStats> rowkeyStats = rowkeyStatsMapper.queryRowKeyStats(cubeId);
        Set<String> rks = new HashSet<>();
        rowkeyStats.forEach(rk -> {
            rks.add(rk.getRowkey());
        });
        Set<String> usedRks = new HashSet<>();
        times.keySet().forEach(k -> {
            usedRks.addAll(new HashSet(Arrays.asList(k.split(","))));
        });
        int total = rks.size();
        int used = usedRks.size();
        rks.removeAll(usedRks);
        analysis.add(AnalysisUtil.getAnalysisRowkey(total, used, rks));

        //cuboid 情况
        CuboidStats cuboidStats = optimizeService.getShrink(cubeId);
        CuboidShrink cs = cuboidStats.getRoot();
        int[] y = new int[2];
        y[0] = 0;
        y[1] = 0;
        Set<Long> set = new HashSet<>();
        queryCuboid(null, cs, y, set);
        List<String> rowkeys = new ArrayList<>();
        set.forEach(l -> {
            rowkeys.add(rowkeyStats.get(rowkeyStats.size() - Long.toBinaryString(l).length()).getRowkey());
        });
        analysis.add(AnalysisUtil.getAnalysisShrink(cuboidStats.getCount().intValue(), y[0], y[1], ProposalsUtil.getString(rowkeys)));

        //分区情况
        List<Segment> segments = cubeMapper.getSegments(cubeId);
        analysis.add(AnalysisUtil.getAnalysisSegment(segments.size(), tf.getFactorSegment().intValue()));

        //构建情况
        if(segments.size() > 0) {
            Segment seg = segments.get(segments.size() - 1);
            KylinJobInfo job = jobMapper.kylinJobInfo(seg.getLastBuildJobId());
            if (job != null && job.getJobTime() > 0) {
                analysis.add(AnalysisUtil.getAnalysisBuild("FULL_BUILD".equals(seg.getName()) ? "全量" : "最后一次增量", ((Number) jobMapper.kylinJobInfo(seg.getLastBuildJobId()).getJobTime()).longValue(), tf.getFactorBuild().longValue()));
            }
        }
    }

    /**
     * 设置调优建议
     * @param cubeSuggest
     * @param cubeId
     */
    private void setProposal(CubeSuggest cubeSuggest, String cubeId) {
        List<String> proposals = new ArrayList<>();
        cubeSuggest.setProposals(proposals);

        //默认建议
        proposals.addAll(ProposalsUtil.getProposalDefault());

        List<String> exists = new ArrayList<>();
        Map<String, String> newgroup = new HashMap<>();
        cubeSuggest.getSuggestGroups().forEach(sg -> {
            if(sg.getGroupType().equals(SuggestGroupType.EXISTS) || sg.getGroupType().equals(SuggestGroupType.EXISTS_BUT_SUGGEST_DELETE)){
                exists.add(sg.getGroupName());
            }else if(sg.getGroupType().equals(SuggestGroupType.SUGGEST_ADD)){
                newgroup.put(sg.getGroupName(), ProposalsUtil.getString(sg.getNewGroupColumns()));
            }
        });

        //已有聚合组调优建议
        if(exists.size() > 0) {
            proposals.add(ProposalsUtil.getProposalExist(ProposalsUtil.getString(exists)));
        }

        //新增聚合组调优建议
        if(newgroup.size() > 0){
            proposals.add(ProposalsUtil.getProposalNew(newgroup));
        }

    }

    /**
     * 处理根据group2是否存在删除group1中的数据
     * @param group1
     * @param group2
     */
    private void dealGroup(List<List<String>> group1, List<List<String>> group2){
        Iterator<List<String>> it = group1.iterator();
        while(it.hasNext()){
            List<String> g1 = it.next();
            for(List<String> g2 : group2){
                if(g1.containsAll(g2) && g2.containsAll(g1)){
                    it.remove();
                    break;
                }
            }
        }
    }

    private void queryCuboid(CuboidShrink parent, CuboidShrink cs, int[] y, Set<Long> set){
        if(parent != null && cs.getRelativeShrink() != null){
            if(cs.getRelativeShrink() > 0.95){
                y[0] += 1;
                if(cs.getRelativeShrink() >= 1.0) {
                    y[1] += 1;
                    set.add(cs.getCuboid() ^ parent.getCuboid());
                }
            }
        }
        List<CuboidShrink> list = cs.getSubCuboids();
        if(list != null && !list.isEmpty()){
            list.forEach(sub -> {
                queryCuboid(cs, sub, y, set);
            });
        }
    }

    private boolean checkByFactorAggregation(Integer d1, Integer d2, Double factor) {
        return Math.min(d1, d2)*1d / Math.max(d1, d2) > factor;
    }

    /**
     * 返回group名称
     * @param size
     * @return
     */
    private String getGroupName(int size){
        return "Group #" + size;
    }
}
